package com.feature.core;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;

import org.aspectj.lang.annotation.Pointcut;
import org.mapstruct.Named;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.TaskDecorator;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;

import com.alibaba.druid.pool.DruidDataSource;
import com.aliyun.oss.OSS;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.feature.core.config.AliyunOssConfig;
import com.feature.core.config.DruidDataSourceConfig;
import com.feature.core.config.LogAspectConfig;
import com.feature.core.config.MapstructTypeConvert;
import com.feature.core.config.MybatisPlusConfig;
import com.feature.core.config.RabbitMqAutoConfigure;
import com.feature.core.config.RedisConfig;
import com.feature.core.config.RedisSessionConfig;
import com.feature.core.config.XxlJobConfig;
import com.feature.core.util.SpringContextUtil;
import com.feature.core.util.TraceIdUtil;
import com.feature.core.util.event.AfterTransactionListener;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;

@Configuration(proxyBeanMethods = false)
public class ApplicationConfig {

    @ConditionalOnClass(DruidDataSource.class)
    @Bean
    public DruidDataSourceConfig druidDataSourceConfig() {
        return new DruidDataSourceConfig();
    }

    @Bean
    public SpringContextUtil springContextUtil() {
        return new SpringContextUtil();
    }

    @ConditionalOnClass(MybatisPlusProperties.class)
    @Configuration
    public static class MyBatisPlusConfigImport extends MybatisPlusConfig {

    }

    @ConditionalOnClass(RedisConnectionFactory.class)
    @Import(RedisConfig.class)
    @Configuration
    public static class RedisConfigImport {

    }

    @ConditionalOnClass(RedisIndexedSessionRepository.class)
    @Import(RedisSessionConfig.class)
    @Configuration
    public static class RedisSessionConfigImport {

    }

    @ConditionalOnClass(MapperScan.class)
    @MapperScan("com.feature.**.mapper")
    @Configuration
    public static class MybatisConfig {

    }

    @ConditionalOnClass(Named.class)
    @Bean
    public MapstructTypeConvert typeConvert() {
        return new MapstructTypeConvert();
    }

    @ConditionalOnClass(RabbitTemplate.class)
    @Import(RabbitMqAutoConfigure.class)
    @Configuration
    public static class RabbitmqConfigImport {

    }

    @ConditionalOnClass(Pointcut.class)
    @EnableAspectJAutoProxy
    @Configuration(proxyBeanMethods = false)
    public static class Aspect {
        @Bean
        LogAspectConfig logAspectConfig() {
            return new LogAspectConfig();
        }
    }

    @Configuration(proxyBeanMethods = false)
    @EnableAsync
    public static class AsyncConfig extends AsyncConfigurerSupport {
        private Logger logger = LoggerFactory.getLogger(getClass());

        @Bean
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setThreadNamePrefix("GlobalAsyncThread-");
            executor.setTaskDecorator(new TaskDecorator() {

                @Override
                public Runnable decorate(Runnable runnable) {
                    String traceId = TraceIdUtil.getTraceId();
                    return new Runnable() {

                        @Override
                        public void run() {
                            TraceIdUtil.setTraceID(traceId);
                            runnable.run();
                        }
                    };
                }
            });
            executor.initialize();
            return executor;
        }

        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new AsyncUncaughtExceptionHandler() {

                @Override
                public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                    logger.error("", ex);
                }
            };
        }

    }

    @ConditionalOnClass(XxlJobSpringExecutor.class)
    @Import(XxlJobConfig.class)
    public static class XxljobImprot {

    }

    @ConditionalOnClass(OSS.class)
    @Import(AliyunOssConfig.class)
    public static class AliyunOssImport {

    }

    @Bean
    public AfterTransactionListener afterTransactionListener() {
        return new AfterTransactionListener();
    }
}
